# _*_ coding:utf-8 _*_
# @Time  : 2022.12.13
# @Author: zizlee
import json
import math
import datetime
import itertools
import pathlib

import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
from copy import deepcopy
from matplotlib.ticker import MultipleLocator, FuncFormatter
from utils import datetime_utils
from settings import FILE_STORAGE, STATIC_HOST

# Agg, is a non-interactive backend that can only write to files.
matplotlib.use('agg')  # 避免警告： UserWarning: Starting a Matplotlib GUI outside of the main thread will likely fail.
plt.rcParams['font.sans-serif'] = ['SimHei']  # 显示中文
plt.rcParams["axes.unicode_minus"] = False


"""
【基础权益、净值】
1. 无出入金
基础权益=上一日调整后的基础权益
单位净值=当日权益/基础权益

2. 有入金，无出金
基础权益=（当天上日结存+当日入金）/上日单位净值
单位净值=当日权益/基础权益

3. 无入金，有出金
单位净值=（当日权益+当日出金)/上一个基础权益
基础权益=当日权益/单位净值

4. 有出入金
单位净值=(当日权益+当日出金)/((当天的上日结存+当日入金)/上日单位净值)
基础权益=当日权益/单位净值

" 单位净值=(当日权益+当日出金)/(当天的上日结存+当日入金)  " 若除数为0，则当日净值为1

【收益率】
1. 无入金
   上日结存不等于0
        利润率=(期末权益-上日结存+出金-入金)/上日结存
   上日结存等于0
        利润率=0  算平均的时候得剔除
2. 入金>0
    利润率=（期末权益-上日结存+出金-入金）/(上日结存+入金)
"""


# 有关资金对账表的分析
class FundReconciler(object):
    TEMP_FOLDER = pathlib.Path(FILE_STORAGE).joinpath('sysTemp/exDoctor')

    def __init__(self, fund_list):
        fund_list = self.calculate_net_value_ratio(fund_list)
        self.fund_df = pd.DataFrame(fund_list)
        self.fund_df['ex_date'] = self.fund_df['ex_date'].astype(str)
        # print(self.fund_df)
        # 一些数据列的计算
        # 5 累计净值
        self.fund_df['cum_net'] = self.fund_df['net'].cumprod().round(6)
        # 1 每日净盈亏
        self.fund_df['day_profit_net'] = self.fund_df['profit']
        # 2 盈亏标记
        self.fund_df['flag'] = self.fund_df['day_profit_net'].apply(lambda p: 1 if p > 0 else -1)
        # 3 最大回撤率
        # self.fund_df['withdraw_ratio'] = ((self.fund_df['net'].max() - self.fund_df['net']) / self.fund_df['net'].max()).round(4)
        # 3 各个回撤区间数据
        self.pct_list = self.get_pct_data()
        # 3 累计回撤（这个数据）
        self.fund_df['cum_max_net'] = self.fund_df['net'].cummax()
        self.fund_df['withdraw_ratio'] = ((self.fund_df['cum_max_net'] - self.fund_df['net']) / self.fund_df['cum_max_net']).round(4)
        # 4 风险度=保证金/权益   也是保证金比例
        self.fund_df['risk_ratio'] = self.fund_df['deposit_fn'] / self.fund_df['equity_end']
        self.fund_df['fund_bail'] = self.fund_df['risk_ratio']
        # self.fund_df['cum_net'] = self.fund_df['net']
        # 6 累计净利润
        self.fund_df['cum_profit'] = self.fund_df['profit'].cumsum().round(2)
        # 净盈亏总额
        self.total_profit = self.fund_df['day_profit_net'].sum().round(2)
        self.fact_start = datetime_utils.auth_yyyymmdd(str(self.fund_df.iloc[0]['ex_date']), obj=True)
        self.fact_end = datetime_utils.auth_yyyymmdd(str(self.fund_df.iloc[-1]['ex_date']), obj=True)
        self.exchange_days = datetime_utils.get_exchange_days(self.fact_start, self.fact_end)
        a = (self.fund_df['equity_begin'] + self.fund_df['fund_in']).mean()  # 期初+入金  均值
        b = (self.fund_df['equity_end'] + self.fund_df['fund_out']).mean()   # 期末+出金  均值
        # c = self.fund_df['profit'].sum() / ((a + b) / 2)  # 期间收益率
        self.interval_profit_ratio = self.fund_df['profit'].sum() / ((a + b) / 2)  # 期间收益率
        # 累计净值
        self.cum_net_value = 0

    @staticmethod
    def calculate_net_value_ratio(data_list):
        row_list = deepcopy(data_list)
        for row in row_list:
            row['base'] = row['equity_begin']
            if row['equity_begin'] + row['fund_in'] == 0:
                row['net'] = 1
            else:
                row['net'] = (row['equity_end'] + row['fund_out']) / (row['equity_begin'] + row['fund_in'])
            # 收益率
            # row['ratio'] = row['profit'] / (row['equity_end'] - row['fund_in'] + row['fund_out'])
            if (row['equity_end'] + row['fund_out']) + (row['equity_begin'] + row['fund_in']) == 0:
                row['ratio'] = 0
            else:
                row['ratio'] = row['profit'] / (((row['equity_end'] + row['fund_out']) + (row['equity_begin'] + row['fund_in'])) / 2)
        return row_list

    # 账户分析一览表
    def account_overview(self, filename_prefix, doc=False):
        fact_start = datetime_utils.auth_yyyymmdd(self.fund_df.iloc[0]['ex_date'], obj=True)
        fact_end = datetime_utils.auth_yyyymmdd(self.fund_df.iloc[-1]['ex_date'], obj=True)
        start_date = fact_start.strftime('%Y-%m-%d')  # 开始日期
        end_date = fact_end.strftime('%Y-%m-%d')  # 结束日期
        equity_begin = self.fund_df.iloc[0]['equity_begin']  # 期初权益
        equity_end = self.fund_df.iloc[-1]['equity_end']  # 期末权益
        fund_bail = self.fund_df.iloc[-1]['deposit_ex']  # 交易所持仓保证金

        fund_in = self.fund_df['fund_in'].sum().round(2)  # 总入金
        fund_out = self.fund_df['fund_out'].sum().round(2)  # 总出金

        exchange_days = len(datetime_utils.get_exchange_days(fact_start, fact_end))  # 交易日个数
        average_day_profit = round(self.total_profit / exchange_days, 2)  # 日均盈亏
        gain_days = self.fund_df[self.fund_df['day_profit_net'] > 0].shape[0]  # 盈利天数
        loss_days = self.fund_df[self.fund_df['day_profit_net'] <= 0].shape[0]  # 亏损天数
        day_gain_max = self.fund_df[self.fund_df['day_profit_net'] >= 0]['day_profit_net'].max().round(2)  # 单日最大盈利
        day_loss_max = self.fund_df[self.fund_df['day_profit_net'] <= 0]['day_profit_net'].min().round(2)  # 单日最大亏损

        max_con_gain_days = max(sum(g) for k, g in itertools.groupby(self.fund_df['flag']) if k == 1)  # 最大连续盈利天数
        max_con_loss_days = max(-sum(g) for k, g in itertools.groupby(self.fund_df['flag']) if k == -1)  # 最大连续亏损天数

        max_net_value = self.fund_df['net'].max().round(4)  # 期间最大净值
        # average_day_profit_ratio = self.fund_df['ratio'].mean()
        average_day_profit_ratio = self.interval_profit_ratio / exchange_days  # 日均收益率=期间收益率/交易日个数
        annualized_profit_ratio = f'{round(average_day_profit_ratio * 250 * 100, 2)}%'  # 预计年化收益率
        std_day_profit_ratio = self.fund_df['ratio'].std()
        annualized_volatility_num = std_day_profit_ratio * math.pow(250, 0.5)
        annualized_volatility = f'{round(annualized_volatility_num * 100, 2)}%'  # 年化波动率
        # 最大回撤率
        mac_pct_data = max(self.pct_list, key=lambda x: x[-2])
        max_withdraw_ratio_num = mac_pct_data[-2] / 100
        std_down_profit_ratio = self.fund_df[self.fund_df['ratio'] < self.fund_df['ratio'].mean()]['ratio'].std()
        annualized_down_volatility_num = std_down_profit_ratio * math.pow(250, 0.5)
        annualized_down_volatility = f'{round(annualized_down_volatility_num * 100, 2)}%'  # 年化下行波动率
        # 夏普比率 = (年化收益率 - 无风险利率) / 组合年化波动率
        sharpe_ratio_num = (average_day_profit_ratio * 250 - 0.03) / annualized_volatility_num
        sharpe_ratio = '%.4f' % sharpe_ratio_num  # 夏普比率
        # 计算索提诺比率，Sortino Ratio=(Rp-Rf)/DR，其中，Rp为平均值，Rf为无风险利率，DR为下行标准差
        sortino_ratio_num = (average_day_profit_ratio * 250 - 0.03) / std_down_profit_ratio
        sortino_ratio = '%.4f' % sortino_ratio_num  # 索提诺比率
        # 卡玛比率 = 年化收益 /最大回撤率
        calmar_ratio_num = (average_day_profit_ratio * 250) / max_withdraw_ratio_num
        calmar_ratio = '%.4f' % calmar_ratio_num  # 卡玛比率
        # 计算风险度=保证金/权益
        average_risk_rate_num = self.fund_df['risk_ratio'].mean()
        risk_rate_average = f'{round(average_risk_rate_num * 100, 2)}%'  # 平均风险度
        # 单位净值
        last_net_value = self.fund_df.iloc[-1]['net']  # 最近单位净值
        last_net_value_str = '%.4f' % self.fund_df.iloc[-1]['net']  # 最近单位净值
        # 累计净值
        self.cum_net_value = round(self.fund_df.iloc[-1]['cum_net'], 4)  # 最后的累计净值
        # 交易日个数
        exchange_days = len(datetime_utils.get_exchange_days(fact_start, fact_end))

        result_data = {
            'overview': [
                {'name': '起始日期', 'key': 'start_date', 'value': start_date},
                {'name': '结束日期', 'key': 'end_date', 'value': end_date},
                {'name': '期初权益', 'key': 'equity_begin', 'value': equity_begin},
                {'name': '期末权益', 'key': 'equity_end', 'value': equity_end},
                {'name': '总入金', 'key': 'fund_in', 'value': fund_in},
                {'name': '总出金', 'key': 'fund_out', 'value': fund_out},
                {'name': '持仓保证金', 'key': 'fund_bail', 'value': fund_bail},
                {'name': '净盈亏总额', 'key': 'total_profit', 'value': self.total_profit},
                {'name': '交易日', 'key': 'exchange_days', 'value': exchange_days},
                {'name': '日均盈亏', 'key': 'average_day_profit', 'value': average_day_profit},
                {'name': '获利天数', 'key': 'gain_days', 'value': gain_days},
                {'name': '亏损天数', 'key': 'loss_days', 'value': loss_days},
                {'name': '单日最大盈利', 'key': 'day_gain_max', 'value': day_gain_max},
                {'name': '单日最大亏损', 'key': 'day_loss_max', 'value': day_loss_max},
                {'name': '最大连续盈利天数', 'key': 'max_con_gain_days', 'value': max_con_gain_days},
                {'name': '最大连续亏损天数', 'key': 'max_con_loss_days', 'value': max_con_loss_days},
                {'name': '日均收益率', 'key': 'average_day_profit_ratio', 'value': average_day_profit_ratio},
                {'name': '年化收益率', 'key': 'annualized_profit_ratio', 'value': average_day_profit_ratio * 250},
                {'name': '年化波动率', 'key': 'annualized_volatility', 'value': annualized_volatility_num},
                {'name': '最大回撤', 'key': 'max_withdraw_ratio', 'value': max_withdraw_ratio_num},
                {'name': '年化下行波动率', 'key': 'annualized_down_volatility', 'value': annualized_down_volatility_num},
                {'name': '夏普比率', 'key': 'sharpe_ratio', 'value': sharpe_ratio_num},
                # {'name': '卡玛比率', 'key': 'calmar_ratio', 'value': calmar_ratio_num},
                # {'name': '索提诺比率', 'key': 'sortino_ratio', 'value': sortino_ratio_num},
                {'name': '期间最大净值', 'key': 'max_net_value', 'value': max_net_value},
                {'name': '单位净值', 'key': 'last_net_value', 'value': last_net_value},
                {'name': '累计净值', 'key': 'cum_net_value', 'value': self.cum_net_value},
                {'name': '平均风险度', 'key': 'average_risk_ratio', 'value': average_risk_rate_num},
            ],
            'fact_start': fact_start.strftime('%Y-%m-%d'),
            'fact_end': fact_end.strftime('%Y-%m-%d'),
            'cum_net_value': round(self.cum_net_value, 4),
            'poem': self.get_encourage_poem()
        }
        for item in result_data['overview']:
            if item['key'] in ['annualized_profit_ratio', 'average_day_profit_ratio', 'annualized_volatility', 'max_withdraw_ratio',
                               'annualized_down_volatility', 'average_risk_ratio']:
                item['plain'] = f'{round(float(item["value"]) * 100, 2)}%'
            elif item['key'] in ['sharpe_ratio', 'max_net_value', 'cum_net_value', 'last_net_value']:
                item['plain'] = f'{round(float(item["value"]), 4)}'
            else:
                item['plain'] = str(item['value'])
        if doc:
            tem_file = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_account_overview.json')
            with open(tem_file, 'w', encoding='utf8') as f:
                json.dump(result_data, f, indent=2, ensure_ascii=False)
        return result_data

    # 累计净值与-当日净利润图-文字(2023.02.03删除)
    def create_cum_net_value_profit(self, filename_prefix, fig_size, xtick_count, only_text=False):
        # 生成文字分析
        max_cum_net_df = self.fund_df[self.fund_df['cum_net'] == self.fund_df['cum_net'].max()]
        max_cum_date = datetime_utils.auth_yyyymmdd(str(max_cum_net_df.iloc[0]['ex_date']), tf='%Y.%m.%d')
        max_cum_value = max_cum_net_df.iloc[0]['cum_net']
        public_text = f"报告期{self.fact_start.strftime('%Y.%m.%d')}至{self.fact_end.strftime('%Y.%m.%d')}，共{len(self.exchange_days)}个交易日，"
        max_profit = self.fund_df['profit'].max()
        mean_profit = self.fund_df['profit'].mean()
        bail_text = public_text + f'其中在{max_cum_date}累计净值达到了最高点{round(max_cum_value, 4)}，'
        bail_text += f'期间最大的当日净利润为{round(max_profit, 2)}，平均每日净利润为{round(mean_profit, 2)}。'
        if only_text:  # 只需要解析的文字
            return {
                'txt_file': '',
                'img_file': '',
                'txt': bail_text
            }
        txt_file = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_cum_net_value_profit.txt')
        with open(txt_file, 'w', encoding='utf8') as f:
            f.write(bail_text)
        x_labels = self.fund_df['ex_date'].tolist()
        x_ticks = self.get_x_axis_ticks(x_labels, xtick_count)
        fig, ax1 = plt.subplots(figsize=fig_size)
        plt.xticks([i for i in range(len(x_labels))], x_ticks, rotation=30, fontsize=9)
        ax1.bar(x_labels, self.fund_df['profit'].tolist(), label='净利润', zorder=10)
        ax2 = ax1.twinx()
        ax2.plot(x_labels, self.fund_df['cum_net'].tolist(), label='累计净值(右)', color='#aa4926')
        ax1.set_ylabel('净利润')
        ax2.set_ylabel('累计净值')
        # # 以下是净利润放右轴
        # ax1.set_xlabel('日期')
        # ax1.set_ylabel('累计净值')
        # ax2 = ax1.twinx()
        # ax2.bar(x_labels, self.fund_df['profit'].tolist(), label='净利润')
        # ax2.set_ylabel('净利润')
        # ax2.patch.set_visible(True)
        # ax1.patch.set_visible(False)
        # ax1.set_zorder(20)
        # ax2.set_zorder(10)
        ax1.legend(loc='upper right', frameon=False, bbox_to_anchor=(0., 1., 0.55, 0.08))
        ax2.legend(loc='upper right', frameon=False, bbox_to_anchor=(0., 1., 1, 0.08))
        # plt.title('累计净值与当日净利润')
        plt.subplots_adjust(top=0.95, right=0.85)
        ax1.grid(axis='y', zorder=0)
        # 去除边框
        for loc_axis in ['left', 'top', 'right']:
            ax1.spines[loc_axis].set_visible(False)
            ax2.spines[loc_axis].set_visible(False)
        img_file = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_cum_net_value_profit.png')
        plt.savefig(img_file)
        plt.close()
        return {
            'txt_file': txt_file,
            'img_file': img_file,
            'txt': ''
        }

    # 累计净值与累计净利润(2023.02.03新增)
    def create_cum_net_value_cum_profit(self, filename_prefix, fig_size, xtick_count, only_text=False):
        # 生成文字分析
        max_cum_net_df = self.fund_df[self.fund_df['cum_net'] == self.fund_df['cum_net'].max()]
        max_cum_net_date = datetime_utils.auth_yyyymmdd(str(max_cum_net_df.iloc[0]['ex_date']), tf='%Y.%m.%d')
        max_cum_net_value = max_cum_net_df.iloc[0]['cum_net']
        min_cum_net_df = self.fund_df[self.fund_df['cum_net'] == self.fund_df['cum_net'].min()]
        min_cum_net_date = datetime_utils.auth_yyyymmdd(str(min_cum_net_df.iloc[0]['ex_date']), tf='%Y.%m.%d')
        min_cum_net_value = min_cum_net_df.iloc[0]['cum_net']

        max_cum_profit = self.fund_df[self.fund_df['cum_profit'] == self.fund_df['cum_profit'].max()]
        max_cum_profit_date = datetime_utils.auth_yyyymmdd(str(max_cum_profit.iloc[0]['ex_date']), tf='%Y.%m.%d')
        max_cum_profit_value = max_cum_profit.iloc[0]['cum_profit']
        min_cum_profit = self.fund_df[self.fund_df['cum_profit'] == self.fund_df['cum_profit'].min()]
        min_cum_profit_date = datetime_utils.auth_yyyymmdd(str(min_cum_profit.iloc[0]['ex_date']), tf='%Y.%m.%d')
        min_cum_profit_value = min_cum_profit.iloc[0]['cum_profit']
        mean_profit = self.fund_df['profit'].mean()

        public_text = f"报告期{self.fact_start.strftime('%Y.%m.%d')}至{self.fact_end.strftime('%Y.%m.%d')}，共{len(self.exchange_days)}个交易日，"
        profit_text = public_text + f'其中在{min_cum_net_date}是累计净值的最低点，为{round(min_cum_net_value, 4)}，在{max_cum_net_date}累计净值达到了最高点{round(max_cum_net_value, 4)}，'
        profit_text += f'在{min_cum_profit_date}是累计净利润最低点，为{round(min_cum_profit_value, 2)}，在{max_cum_profit_date}累计净利润达到了最高点{round(max_cum_profit_value, 2)}，'
        profit_text += f'平均每日净利润为{round(mean_profit, 2)}。'
        if only_text:
            return {
                'txt_file': '',
                'img_file': '',
                'txt': profit_text
            }
        txt_file = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_cum_net_value_cum_profit.txt')
        with open(txt_file, 'w', encoding='utf8') as f:
            f.write(profit_text)
        # 生成图片
        x_labels = self.fund_df['ex_date'].tolist()
        x_index, x_ticks = self.get_x_axis_ticks(x_labels, xtick_count)

        fig, ax1 = plt.subplots(figsize=fig_size)
        plt.xticks(x_index, x_ticks, rotation=30, fontsize=9)
        ax1.bar(x_labels, self.fund_df['cum_profit'].tolist(), label='累计净利润', zorder=10)
        ax2 = ax1.twinx()
        ax2.plot(x_labels, self.fund_df['cum_net'].tolist(), label='累计净值(右)', color='#aa4926')
        ax1.set_ylabel('累计净利润')
        ax2.set_ylabel('累计净值')
        ax1.legend(loc='upper right', frameon=False, bbox_to_anchor=(0., 1., 0.55, 0.08))
        ax2.legend(loc='upper right', frameon=False, bbox_to_anchor=(0., 1., 1, 0.08))
        # plt.title('累计净值与当日净利润')
        plt.subplots_adjust(top=0.95, right=0.85)
        ax1.grid(axis='y', zorder=0)
        # 去除边框
        for loc_axis in ['left', 'top', 'right']:
            ax1.spines[loc_axis].set_visible(False)
            ax2.spines[loc_axis].set_visible(False)
        img_file = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_cum_net_value_cum_profit.png')
        plt.savefig(img_file)
        plt.close()
        img_local = str(img_file).replace('\\', '/').replace(FILE_STORAGE, '')
        img_url = STATIC_HOST + img_local
        return {
            'txt_file': str(txt_file).replace('\\', '/').replace(FILE_STORAGE, ''),
            'img_file': img_local,
            'img_url': img_url,
            'txt': profit_text
        }

    # 创建累计净值-保证金比例-图-文字解析
    def create_cum_net_value_bail_image(self, filename_prefix, fig_size, xtick_count):
        x_labels = self.fund_df['ex_date'].tolist()
        x_ticks = self.get_x_axis_ticks(x_labels, xtick_count)
        fig, ax1 = plt.subplots(figsize=fig_size)
        plt.xticks([i for i in range(len(x_labels))], x_ticks, rotation=30, fontsize=9)
        ax1.plot(x_labels, self.fund_df['cum_net'].tolist(), label='累计净值', color='#aa4926')
        ax1.set_xlabel('日期')
        ax1.set_ylabel('累计净值')
        ax2 = ax1.twinx()
        ax2.bar(x_labels, self.fund_df['fund_bail'].tolist(), label='保证金')
        ax2.set_ylabel('保证金比例')
        ax2.patch.set_visible(True)
        ax1.patch.set_visible(False)
        ax1.set_zorder(5)
        ax2.set_zorder(1)
        ax1.legend(loc='upper right', frameon=False, bbox_to_anchor=(0., 1., 0.75, 0.))
        ax2.legend(loc='upper right', frameon=False)
        plt.title('累计净值与持仓保证金')
        plt.savefig(f'temp/images/{filename_prefix}_cum_net_fund_bail.png')
        # 生成文字分析
        max_cum_net_df = self.fund_df[self.fund_df['cum_net'] == self.fund_df['cum_net'].max()]
        max_cum_date = datetime_utils.auth_yyyymmdd(str(max_cum_net_df.iloc[0]['ex_date']), tf='%Y.%m.%d')
        max_cum_value = max_cum_net_df.iloc[0]['cum_net']
        public_text = f"报告期{self.fact_start.strftime('%Y.%m.%d')}至{self.fact_end.strftime('%Y.%m.%d')}，共{len(self.exchange_days)}个交易日，"
        max_bail_ratio = self.fund_df['fund_bail'].max()
        mean_bail_ratio = self.fund_df[self.fund_df['fund_bail'] > 0]['fund_bail'].mean()
        bail_text = public_text + f'其中在{max_cum_date}累计净值达到了最高点{round(max_cum_value, 4)}，'
        bail_text += f'期间最大的保证金占用为{round(max_bail_ratio * 100, 2)}%，剔除比例为0的日期后，平均保证金占用为{round(mean_bail_ratio * 100, 2)}%。'
        with open(f'temp/txt/{filename_prefix}_cum_net_fund_bail.txt', 'w', encoding='utf8') as f:
            f.write(bail_text)

    # 求各个回撤区间
    def get_pct_data(self):
        # 计算累计净值的回撤数据
        df = self.fund_df.copy()
        df.sort_values(by='ex_date', ascending=True, inplace=True)
        df['count'] = range(df.shape[0])
        res_list = []
        temp_hv = None  # 记录当前最大值
        temp_hv_date = None  # 记录当前最大值对应的日期
        temp_hv_loc = None  # 记录当前最大值所在的位置
        temp_lv = None  # 记录当前最小值
        temp_lv_date = None  # 记录当前最小值对应的日期
        temp_lv_loc = None  # 记录当前最小值所在的位置
        temp_duration = None  # 最大值与最小值之间的差

        for i, row in df.iterrows():
            if temp_hv is None:
                temp_hv = row['cum_net']
                temp_hv_date = row['ex_date']
                temp_hv_loc = row['count']
                temp_lv = row['cum_net']
                temp_lv_date = row['ex_date']
                temp_lv_loc = row['count']
                temp_duration = 0
            else:
                if row['cum_net'] > temp_hv:
                    # 如果值大于此前的最大值，说明要进行新一轮的回撤，记录当前这一轮回撤的信息
                    # 计算回撤百分比
                    temp_pct = (temp_duration / temp_hv) * 100
                    res_list.append([temp_hv_loc, temp_hv_date, temp_hv, temp_lv_loc, temp_lv_date, temp_lv, temp_pct,
                                     temp_duration])
                    temp_hv = row['cum_net']
                    temp_hv_date = row['ex_date']
                    temp_hv_loc = row['count']
                    temp_lv = row['cum_net']
                    temp_lv_date = row['ex_date']
                    temp_lv_loc = row['count']
                    temp_duration = 0
                else:
                    if row['cum_net'] <= temp_lv:
                        # 当前值小于此前的最小值，说明数值还在往下走
                        temp_lv = row['cum_net']
                        temp_lv_date = row['ex_date']
                        temp_lv_loc = row['count']
                        temp_duration = temp_hv - temp_lv
        # 最后一次的回撤：最后的价格一直没有超过此前一次的最高价，最后一次的回撤会无法触发记录的条件
        temp_pct = (temp_duration / temp_hv) * 100
        res_list.append([temp_hv_loc, temp_hv_date, temp_hv, temp_lv_loc, temp_lv_date, temp_lv, temp_pct, temp_duration])
        return res_list

    # 回撤图形-文字(2023.02.03新增,2023.02.17修改)
    def create_value_pullback(self, filename_prefix, fig_size, xtick_count, only_text=False):
        # 找出最大回撤
        max_pct = max(self.pct_list, key=lambda x: x[-2])
        max_day_ntv_date = datetime_utils.auth_yyyymmdd(max_pct[1], tf='%Y-%m-%d')
        min_day_ntv_date = datetime_utils.auth_yyyymmdd(max_pct[4], tf='%Y-%m-%d')
        p_ratio = round(max_pct[-2], 2)
        public_text = f"报告期{self.fact_start.strftime('%Y.%m.%d')}至{self.fact_end.strftime('%Y.%m.%d')}，共{len(self.exchange_days)}个交易日，"
        pullback_text = public_text + f'最大回撤期间为{max_day_ntv_date}至{min_day_ntv_date}，最大回撤为{p_ratio}%。'
        if only_text:
            return {
                'txt_file': '',
                'img_file': '',
                'txt': pullback_text
            }
        txt_file = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_cum_pullback.txt')
        with open(txt_file, 'w', encoding='utf8') as f:
            f.write(pullback_text)

        # 抽取出回撤大于等于10%的区间，并在图中标出
        final_list = []
        for item in self.pct_list:
            if item[-2] >= max_pct[-2]:
                pct_ = round(item[-2], 2)
                final_list.append({
                    "min_x": item[0],
                    "min_y": item[2],
                    "max_x": item[3],
                    "max_y": item[5],
                    "show_min_str": item[1],
                    "show_max_str": item[4],
                    "withdraw": str(pct_) + '%'
                })
        # xs = df['ex_date'].values.tolist()
        ys = self.fund_df['cum_net'].values.tolist()

        x_labels = self.fund_df['ex_date'].tolist()
        x_index, x_ticks = self.get_x_axis_ticks(x_labels, xtick_count)  # 只显示指定数量
        fig, ax1 = plt.subplots(figsize=fig_size)
        plt.xticks(x_index, x_ticks, rotation=30, fontsize=9)
        plt.yticks(fontsize=9)
        plt.title('累计净值最大回撤')  # title是在最后一个子图设置， suptitle设置主图标题
        ax1.plot(x_labels, ys, label='累计净值', color='#aa4926')
        ax1.grid(axis='y')
        ax1.set_ylabel('累计净值', loc='top')
        # ax1.yaxis.set_major_formatter(FuncFormatter(lambda x, p: f'{int(x)}%'))  # 设置显示
        plt.grid(axis='y', zorder=0)
        # # 设置y轴刻度
        # if max_pct[-2] > 15:
        #     tick_step = int(max(max_pct[-2]) // 5)
        #     if tick_step > 20:
        #         tick_step = tick_step // 10 * 10
        #     ax1.yaxis.set_major_locator(MultipleLocator(tick_step))  # 设置主刻度
        for item in final_list:
            min_x = item['min_x']
            min_y = item['min_y']
            max_x = item['max_x']
            max_y = item['max_y']
            show_min_str = item['show_min_str']
            show_max_str = item['show_max_str']
            withdraw = item['withdraw']

            plt.scatter(min_x, min_y, color='r')  # 标记最低点
            plt.scatter(max_x, max_y, color='r')  # 标记最高点
            plt.annotate(show_min_str, xytext=(min_x, min_y), xy=(min_x, min_y))  # 标记提示
            plt.annotate(show_max_str, xytext=(max_x, max_y), xy=(max_x, max_y))  # 标记提示

            plt.plot([min_x, max_x], [min_y, max_y], color='b', linestyle='--')  # 连接最低净值点和最高净值点
            plt.annotate(withdraw, xytext=((max_x + min_x) / 2, (max_y + min_y) / 2),
                         xy=((max_x + min_x) / 2, (max_y + min_y) / 2))  # 标记提示

        for loc_axis in ['left', 'top', 'right']:
            ax1.spines[loc_axis].set_visible(False)
        img_file = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_cum_pullback.png')
        plt.savefig(img_file)
        plt.close()
        img_local = str(img_file).replace('\\', '/').replace(FILE_STORAGE, '')
        img_url = STATIC_HOST + img_local
        return {
            'txt_file': str(txt_file).replace('\\', '/').replace(FILE_STORAGE, ''),
            'img_file': img_local,
            'img_url': img_url,
            'txt': pullback_text
        }

    # 当日净值-波动率-图-文字
    def create_day_net_value_volatility(self, filename_prefix, fig_size, xtick_count, only_text=False):
        ntv_list = self.fund_df['net'].tolist()
        ntv_std_list = []
        for row in range(1, len(ntv_list) + 1):
            s = pd.Series(ntv_list[:row])
            ntv_std_list.append(round(s.std() * 100, 4))

        std_series = pd.Series(ntv_std_list)
        std_series.fillna(0, inplace=True)
        self.fund_df['volatility'] = std_series

        # 波动率文字分析
        max_volatility = self.fund_df['volatility'].max()
        mean_volatility = self.fund_df['volatility'].mean()
        max_net_value = self.fund_df['net'].max()
        max_day_ntv_df = self.fund_df[self.fund_df['net'] == max_net_value]
        max_day_ntv_date = datetime_utils.auth_yyyymmdd(str(max_day_ntv_df.iloc[0]['ex_date']), tf='%Y.%m.%d')
        public_text = f"报告期{self.fact_start.strftime('%Y.%m.%d')}至{self.fact_end.strftime('%Y.%m.%d')}，共{len(self.exchange_days)}个交易日，"
        volatility_text = public_text + f'其中在{max_day_ntv_date}单位净值最高为{round(max_net_value, 4)}，'
        volatility_text += f'期间波动率最大为{round(max_volatility, 2)}%，平均波动率为{round(mean_volatility, 2)}%。'
        if only_text:
            return {'txt_file': '', 'img_file': '', 'txt': volatility_text}
        txt_file = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_net_volatility.txt')
        with open(txt_file, 'w', encoding='utf8') as f:
            f.write(volatility_text)
        x_labels = self.fund_df['ex_date'].tolist()
        x_index, x_ticks = self.get_x_axis_ticks(x_labels, xtick_count)
        fig, ax1 = plt.subplots(figsize=fig_size)  # 550*375
        plt.xticks(x_index, x_ticks, rotation=30, fontsize=9)
        ax1.plot(x_labels, self.fund_df['net'].tolist(), label='当日净值', color='#aa4926', zorder=5)
        ax1.set_xlabel('日期')
        ax1.set_ylabel('当日净值')

        ax2 = ax1.twinx()
        ax2.plot(x_labels, self.fund_df['volatility'].tolist(), label='波动率%(右)', zorder=1)
        ax2.set_ylabel('波动率(%)')
        ax2.patch.set_visible(True)
        ax1.patch.set_visible(False)

        ax1.set_zorder(5)
        ax2.set_zorder(1)
        ax1.legend(loc='upper right', frameon=False, bbox_to_anchor=(0., 1., 0.7, 0.))
        ax2.legend(loc='upper right', frameon=False)
        plt.title('当日净值与波动率')
        for loc_axis in ['left', 'top', 'right']:
            ax1.spines[loc_axis].set_visible(False)
            ax2.spines[loc_axis].set_visible(False)
        ax1.grid(axis='y')
        img_file = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_net_volatility.png')
        plt.savefig(img_file)
        plt.close()
        img_local = str(img_file).replace('\\', '/').replace(FILE_STORAGE, '')
        img_url = STATIC_HOST + img_local
        return {
            'txt_file': str(txt_file).replace('\\', '/').replace(FILE_STORAGE, ''),
            'img_file': img_local,
            'img_url': img_url,
            'txt': volatility_text
        }

    # 周期盈亏
    def create_period_profit(self, filename_prefix, fig_size, xtick_count):
        public_text = f"报告期{self.fact_start.strftime('%Y.%m.%d')}至{self.fact_end.strftime('%Y.%m.%d')}，共{len(self.exchange_days)}个交易日，"
        daily_df = self.fund_df[['ex_date', 'day_profit_net']].copy()
        average_day_profit = daily_df['day_profit_net'].mean().round(2)
        d_flag = '盈利' if average_day_profit > 0 else '亏损'
        date_text = public_text + f"平均每日{d_flag}{average_day_profit}元，"
        # 日盈亏最值
        date_max_gain = daily_df[daily_df['day_profit_net'] > 0]['day_profit_net'].max()
        date_min_gain = daily_df[daily_df['day_profit_net'] > 0]['day_profit_net'].min()
        date_max_loss = daily_df[daily_df['day_profit_net'] <= 0]['day_profit_net'].min()
        date_min_loss = daily_df[daily_df['day_profit_net'] <= 0]['day_profit_net'].max()
        date_text += f'其中最大盈利为{round(date_max_gain, 2)}元，最小盈利为{round(date_min_gain, 2)}元，'
        date_text += f'最大亏损为{round(date_max_loss, 2)}元，最小亏损为{round(date_min_loss, 2)}元。'

        # 日盈亏-图形
        day_x_labels = self.fund_df['ex_date'].tolist()
        day_x_index, day_x_ticks = self.get_x_axis_ticks(day_x_labels, xtick_count)
        fig, ax1 = plt.subplots(figsize=fig_size)
        plt.xticks(day_x_index, day_x_ticks, rotation=30, fontsize=9)
        ax1.bar(day_x_labels, daily_df['day_profit_net'].tolist(), label='当日盈亏', zorder=10)
        ax1.set_xlabel('日期')
        ax1.set_ylabel('盈亏')
        plt.title('每日盈亏')
        for loc_axis in ['left', 'top', 'right']:
            ax1.spines[loc_axis].set_visible(False)
        plt.grid(axis='y', zorder=0)

        daily_img = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_period_profit_daily.png')
        plt.savefig(daily_img)
        with open(self.TEMP_FOLDER.joinpath(f'{filename_prefix}_period_profit_daily.txt'), 'w', encoding='utf8') as f:
            f.write(date_text)

        # 周盈亏
        self.fund_df['week'] = self.fund_df['ex_date'].apply(lambda p: datetime.datetime.strptime(str(p), '%Y%m%d').strftime('%W'))
        week_df = self.fund_df.groupby(by='week', as_index=False)['day_profit_net'].sum().round(2)
        week_df = pd.merge(week_df, self.fund_df.groupby(by='week', as_index=False).last()[['week', 'ex_date']],
                           on='week', how='left')
        week_df = week_df[['ex_date', 'day_profit_net']].copy()
        exchange_weeks = week_df['ex_date'].tolist()
        average_week_profit = week_df['day_profit_net'].mean().round(2)
        w_flag = '盈利' if average_week_profit > 0 else '亏损'
        week_text = public_text + f"共{len(exchange_weeks)}周，平均每周{w_flag}{average_week_profit}元，"
        week_max_gain = week_df[week_df['day_profit_net'] > 0]['day_profit_net'].max()
        week_min_gain = week_df[week_df['day_profit_net'] > 0]['day_profit_net'].min()
        week_max_loss = week_df[week_df['day_profit_net'] <= 0]['day_profit_net'].min()
        week_min_loss = week_df[week_df['day_profit_net'] <= 0]['day_profit_net'].max()
        week_text += f'其中最大盈利为{round(week_max_gain, 2)}元，最小盈利为{round(week_min_gain, 2)}元，'
        week_text += f'最大亏损为{round(week_max_loss, 2)}元，最小亏损为{round(week_min_loss, 2)}元。'

        week_x_labels = week_df['ex_date'].tolist()
        # 周盈亏-图形
        week_x_index, week_x_ticks = self.get_x_axis_ticks(week_x_labels, xtick_count)
        fig, ax1 = plt.subplots(figsize=fig_size)
        plt.xticks(week_x_index, week_x_ticks, rotation=30, fontsize=9)
        ax1.bar(week_x_labels, week_df.iloc[:]['day_profit_net'], label='每周盈亏', zorder=10)
        ax1.set_xlabel('日期')
        ax1.set_ylabel('盈亏')
        plt.title('每周盈亏')
        for loc_axis in ['left', 'top', 'right']:
            ax1.spines[loc_axis].set_visible(False)
        plt.grid(axis='y', zorder=0)
        week_img = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_period_profit_week.png')
        plt.savefig(week_img)
        with open(self.TEMP_FOLDER.joinpath(f'{filename_prefix}_period_profit_week.txt'), 'w', encoding='utf8') as f:
            f.write(week_text)

        # 月盈亏
        self.fund_df['month'] = self.fund_df['ex_date'].apply(lambda p: str(p)[:6])
        month_df = self.fund_df.groupby(by='month', as_index=False)['day_profit_net'].sum().round(2)
        month_df = pd.merge(month_df, self.fund_df.groupby(by='month', as_index=False).last()[['month', 'ex_date']],
                            on='month', how='left')
        month_df = month_df[['ex_date', 'day_profit_net']].copy()
        exchange_month = month_df['ex_date'].tolist()
        average_month_profit = month_df['day_profit_net'].mean().round(2)
        m_flag = '盈利' if average_month_profit > 0 else '亏损'
        month_text = public_text + f"共{len(exchange_month)}月，平均每月{m_flag}{average_month_profit}元，"
        month_max_gain = month_df[month_df['day_profit_net'] > 0]['day_profit_net'].max()
        month_min_gain = month_df[month_df['day_profit_net'] > 0]['day_profit_net'].min()
        month_max_loss = month_df[month_df['day_profit_net'] <= 0]['day_profit_net'].min()
        month_min_loss = month_df[month_df['day_profit_net'] <= 0]['day_profit_net'].max()
        month_text += f'其中最大盈利为{round(month_max_gain, 2)}元，最小盈利为{round(month_min_gain, 2)}元，'
        month_text += f'最大亏损为{round(month_max_loss, 2)}元，最小亏损为{round(month_min_loss, 2)}元。'

        month_x_labels = month_df['ex_date'].tolist()
        month_x_index, month_x_ticks = self.get_x_axis_ticks(month_x_labels, xtick_count)
        # 月盈亏-图形
        fig, ax1 = plt.subplots(figsize=fig_size)
        plt.xticks(month_x_index, month_x_ticks, rotation=30, fontsize=9)
        ax1.bar(month_x_labels, month_df['day_profit_net'].tolist(), label='每月盈亏', zorder=10)
        ax1.set_xlabel('日期')
        ax1.set_ylabel('盈亏')
        plt.title('每月盈亏')
        for loc_axis in ['left', 'top', 'right']:
            ax1.spines[loc_axis].set_visible(False)
        plt.grid(axis='y', zorder=0)
        month_img = self.TEMP_FOLDER.joinpath(f'{filename_prefix}_period_profit_month.png')
        plt.savefig(month_img)
        with open(self.TEMP_FOLDER.joinpath(f'{filename_prefix}_period_profit_month.txt'), 'w', encoding='utf8') as f:
            f.write(month_text)
        plt.close()
        daily_img_file = str(daily_img).replace('\\', '/').replace(FILE_STORAGE, '')
        daily_img_url = STATIC_HOST + daily_img_file
        week_img_file = str(week_img).replace('\\', '/').replace(FILE_STORAGE, '')
        week_img_url = STATIC_HOST + week_img_file
        month_img_file = str(month_img).replace('\\', '/').replace(FILE_STORAGE, '')
        month_img_url = STATIC_HOST + month_img_file
        return {
            'daily_txt': date_text,
            'daily_img_file': daily_img_file,
            'daily_img_url': daily_img_url,
            'week_txt': week_text,
            'week_img_file': week_img_file,
            'week_img_url': week_img_url,
            'month_txt': month_text,
            'month_img_file': month_img_file,
            'month_img_url': month_img_url
        }

    @staticmethod
    def get_x_axis_ticks(x_labels, xtick_count):
        gap_num = math.ceil(len(x_labels) / xtick_count)
        if gap_num == 0:
            gap_num = 1
        x_ticks = [x for i, x in enumerate(x_labels) if i % gap_num == 0]
        x_ticks[-1] = x_labels[-1]
        x_index = [i for i in range(len(x_labels)) if i % gap_num == 0]
        x_index[-1] = len(x_labels) - 1
        return x_index, x_ticks

    def get_encourage_poem(self):
        if self.cum_net_value < 0.6:
            poem = '沉舟侧畔千帆过，病树前头万木春。'
        elif 0.6 <= self.cum_net_value < 0.8:
            poem = '采菊东篱下，悠然见南山。'
        elif 0.8 <= self.cum_net_value < 1.2:
            poem = '路漫漫其修远兮，吾将上下而求索。'
        elif 1.2 <= self.cum_net_value < 1.8:
            poem = '龙岂池中物，乘雷欲上天。'
        else:
            poem = '九万里风鹏正举。风休住，蓬舟吹取三山去！'
        return poem
